Categories
Vue 3 Testing

Testing Vue 3 Apps — Test with Real Router and Stub Components

Spread the love

With apps getting more complex than ever, it’s important to test them automatically. We can do this with unit tests, and then we don’t have to test everything by hand.

In this article, we’ll look at how to test Vue 3 apps by writing a simple app and testing it.

Testing With a Real Router

We can write tests that test our app with the real router.

For example, we can write:

src/router/index.js

import { createRouter, createWebHistory } from 'vue-router'
import Home from '../views/Home.vue'
import Edit from '../views/Edit.vue'
import Post from '../views/Post.vue'

const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/edit',
    name: 'edit',
    component: Edit
  },
  {
    path: '/posts/:id/edit',
    name: 'post',
    component: Post
  },
]

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes
})

export default router

src/views/Edit.vue

<template>
  <button @click="redirect">Click to Edit</button>
</template>

<script>
export default {
  methods: {
    redirect() {
      this.$router.push(`/posts/1/edit`);
    },
  },
};
</script>

src/views/Post.vue

<template>
  <div>Post {{$route.params.id}}</div>
</template>

tests/unit/example.spec.js

import { flushPromises, mount } from '@vue/test-utils'
import App from '@/App'
import router from '@/router'

describe('component handles routing correctly', () => {
  it('allows authenticated user to edit a post', async () => {
    router.push('/edit')
    await router.isReady()
    const wrapper = mount(App, {
      global: {
        plugins: [router],
      }
    })
    await wrapper.find('button').trigger('click')
    await flushPromises()
    expect(wrapper.html()).toContain('Post 1')
  })
})

We have some routes mapped to some components.

Then in our test, we redirect to the edit route so the Edit component is loaded.

Next, we call router.isReady() to wait until the Vue Router is loaded.

Then we mount the App and pass in the router into the plugins property.

Then we trigger a click on the button in the Edit component.

And finally, we check what’s rendered after calling flushPromises to wait until the new route is loaded.

Stubbing a Single Child Component

We can stub child components in our app.

For example, we can write:

import { mount } from '@vue/test-utils'
import axios from 'axios';

const FetchDataFromApi = {
  name: 'FetchDataFromApi',
  template: `
    <div>{{ result }}</div>
  `,
  async mounted() {
    const res = await axios.get('https://yesno.wtf/api')
    this.result = res.data
  },
  data() {
    return {
      result: ''
    }
  }
}

const App = {
  components: {
    FetchDataFromApi
  },
  template: `
    <div>
      <h1>Welcome to Vue.js 3</h1>
      <fetch-data-from-api />
    </div>
  `
}

test('stubs component with custom template', () => {
  const wrapper = mount(App, {
    global: {
      stubs: {
        FetchDataFromApi: {
          template: '<span />'
        }
      }
    }
  })
  expect(wrapper.html()).toContain('Welcome to Vue.js 3')
})

We have the FetchDataFromApi component that we want to ignore in our test.

It’s used in App , but we don’t want to mount it in our test.

Therefore, we want to stub this component. To do this, we pass in a stubbed component into the global.stubs property.

Then we check the content of App in the last line of the test.

Conclusion

We can test our components with a real Vue Router with Vue 3 components.

Also, we can stub components and test only the parts we want to test.

By John Au-Yeung

Web developer specializing in React, Vue, and front end development.

Leave a Reply

Your email address will not be published. Required fields are marked *